import isPromise from '../utils/isPromise';
import PromiseCounter from './promiseCounter';
import { showLoading, hideLoading } from '../actions/loading';

function _showLoading(dispatch) {
  if (PromiseCounter.isEmpty()) {
    dispatch(showLoading());
  }

  PromiseCounter.countUp();
}

function _hideLoading(dispatch) {
  // make sure the count down the PromiseCounter in another event loop
  // to handle we chain the ajax call.
  setTimeout(() => {
    PromiseCounter.countDown();

    // if there is no promise, we relase the loading bar
    if (PromiseCounter.isEmpty()) {
      dispatch(hideLoading());
    }
  }, 0);
}

export default ({ dispatch, getState }) => next => (action) => {
  // We wish all actions follow the FSA standard.
  // We will add strict FSA checking here in later time.
  if (!action.payload) {
    return next(action);
  }
  const promise = action.payload.promise || action.payload;
  if (!isPromise(promise)) {
    return next(action);
  }

  let ignoreLoadingbar = false;
  let handlers = null;

  if (action.meta) {
    ignoreLoadingbar = !!action.meta.ignoreLoadingbar;
    handlers = action.meta.handlers;
  }

  if (!ignoreLoadingbar) {
    _showLoading(dispatch);
  }


  const reportError = (error) => {
    dispatch({
      ...action,
      payload: error,
      error: true
    });
    if (!ignoreLoadingbar) {
      _hideLoading(dispatch);
    }

    if (handlers) {
      if (handlers.failed) {
        handlers.failed(dispatch, getState, error);
      }

      if (handlers.finally) {
        handlers.finally(dispatch, getState, error);
      }
    }

    throw error;
  };

  return promise.then((result) => {
    dispatch({
      ...action,
      payload: result
    });

    if (!ignoreLoadingbar) {
      _hideLoading(dispatch);
    }

    if (handlers) {
      if (handlers.success) {
        handlers.success(dispatch, getState, result);
      }

      if (handlers.finally) {
        handlers.finally(dispatch, getState, result);
      }
    }
  }, (error) => {
    reportError(error);
  }).catch((error) => {
    reportError(error);
  });
};
